package com.xzm.utils;

import lombok.extern.slf4j.Slf4j;

import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.Date;
import java.util.Locale;

/**
 * @author 瞎琢磨先生 https://github.com/xiazuomo/xzm-utils.git
 * @date 2022/7/29 23:51
 */
@Slf4j
public class DateTimeUtils {

    private DateTimeUtils() {
    }

    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
            .withZone(ZoneId.systemDefault())
            .withLocale(Locale.CHINA);

    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    private static final String ERROR_MSG = "输入内容有误！当前仅支持 yyyy-MM-dd、yyyy/MM/dd 格式的字符串、Date 类型的日期、LocalDate 以及 InStant 类型的日期)";

    /**
     * 获取某日期是周几(第几天)
     *
     * @param date eg. 2022-11-20
     * @return eg. 7(周日)
     */
    public static int getDayOfWeek(String date) {
        return convertStrToLD(date).getDayOfWeek().getValue();
    }

    public static int getDayOfWeek(LocalDate date) {
        return date.getDayOfWeek().getValue();
    }

    /**
     * 获取某日期对应在月份是几号
     *
     * @param date 日期。eg. 2022-11-20
     * @return eg. 20
     */
    public static int getDayOfMonth(String date) {
        return convertStrToLD(date).getDayOfMonth();
    }

    public static int getDayOfMonth(LocalDate date) {
        return date.getDayOfMonth();
    }

    /**
     * 判断日期是否在范围内
     *
     * @param sourceDate 被比较日期
     * @param startDate  开始日期
     * @param endDate    结束日期
     */
    public static <T> boolean checkDateRange(T sourceDate, T startDate, T endDate) {
        LocalDate sourceDateNew = convertToLD(sourceDate);
        LocalDate startDateNew = convertToLD(startDate).minusDays(1);
        LocalDate endDateNew = convertToLD(endDate).plusDays(1);
        return sourceDateNew.isBefore(endDateNew) && sourceDateNew.isAfter(startDateNew);
    }

    /**
     * 获取某天的开始时间
     */
    public static <T> String getDayStartTime(T t) {
        LocalDate date = convertToLD(t);
        return LocalDateTime.of(date, LocalTime.MIN).format(DATE_TIME_FORMATTER);
    }

    /**
     * 获取某天的结束时间
     */
    public static <T> String getDayEndTime(T t) {
        LocalDate date = convertToLD(t);
        return LocalDateTime.of(date, LocalTime.MAX).format(DATE_TIME_FORMATTER);
    }

    /**
     * 所在周的周一(Monday)
     */
    public static <T> String getWeekStartDate(T t) {
        LocalDate date = convertToLD(t);
        return convertLDToLDStr(date.with(DayOfWeek.MONDAY));
    }

    /**
     * 所在周的周日(Sunday)
     */
    public static <T> String getWeekEndDate(T t) {
        LocalDate date = convertToLD(t);
        return convertLDToLDStr(date.with(DayOfWeek.SUNDAY));
    }

    /**
     * 获取日期的月初
     */
    public static <T> String getMonthStartDate(T t) {
        LocalDate date = convertToLD(t);
        return convertLDToLDStr(date.with(TemporalAdjusters.firstDayOfMonth()));
    }

    /**
     * 获取日期的月末
     */
    public static <T> String getMonthEndDate(T t) {
        LocalDate date = convertToLD(t);
        return convertLDToLDStr(date.with(TemporalAdjusters.lastDayOfMonth()));
    }

    /**
     * 功能：获取每月的天数
     */
    public static <T> int getMonthCount(T t) {
        // 获取每月的天数
        return convertToLD(t).lengthOfMonth();
    }

    // 校验 8位字符串是否为正确的日期格式
    public static boolean isValidDate(String date) {
        boolean result = true;
        // 判断字符串长度是否为 8 位
        if (date.length() == 8) {
            // 指定日期格式为四位年/两位月份/两位日期，注意 yyyy/MM/dd 区分大小写；
            SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
            try {
                // 设置 lenient 为 false，否则 SimpleDateFormata 会比较宽松地验证日期，比如 2007/02/29 会被接受，并转换成 2007/03/01
                format.setLenient(false);
                format.parse(date);
            } catch (Exception e) {
                // 如果 throw java.text.ParseException 或者 NullPointerException，就说明格式不对
                result = false;
            }
        } else {
            result = false;
        }
        return result;
    }

    /**
     * 将任意类型转成 LocalDate
     */
    public static <T> LocalDate convertToLD(T t) {
        if (t instanceof String) {
            return convertStrToLD((String) t);
        } else if (t instanceof Date) {
            return convertDateToLD((Date) t);
        } else if (t instanceof LocalDate) {
            return (LocalDate) t;
        } else if (t instanceof LocalDateTime) {
            return convertLDTToLD((LocalDateTime) t);
        } else if (t instanceof Instant) {
            return convertInstantToLD((Instant) t);
        }
        throw new RuntimeException("当前日期格式/类型不支持转换");

    }

    /**
     * 功能：将其它类型日期转成 LocalDateTime
     */
    public static <T> LocalDateTime convertToLDT(T t) {
        if (t instanceof String) {
            return converStrToLDT((String) t);
        } else if (t instanceof LocalDate) {
            return convertLDToLDT((LocalDate) t);
        } else if (t instanceof LocalDateTime) {
            return (LocalDateTime) t;
        } else if (t instanceof Date) {
            return convertDateToLDT((Date) t);
        } else if (t instanceof Instant) {
            return convertInstantToLDT((Instant) t);
        }
        throw new RuntimeException("当前日期格式不支持");
    }

    public static <T> Date convertToDate(T t) {
        if (t instanceof String) {
            return convertStrToDate((String) t);
        } else if (t instanceof Date) {
            return (Date) t;
        } else if (t instanceof LocalDate) {
            return convertLDToDate((LocalDate) t);
        } else if (t instanceof LocalDateTime) {// 主
            return convertLDTToDate((LocalDateTime) t);
        } else if (t instanceof Instant) {
            return convertInstantToDate((Instant) t);
        }
        throw new RuntimeException("当前日期格式/类型不支持转换");
    }

    public static <T> Instant convertToInstant(T t) {
        if (t instanceof String) {
            return convertStrToInstant((String) t);
        } else if (t instanceof Date) {
            return convertDateToInstant((Date) t);
        } else if (t instanceof LocalDate) {
            return converLDToInstant((LocalDate) t);
        } else if (t instanceof LocalDateTime) {// (主)
            return convertLDTToInstant((LocalDateTime) t);
        } else if (t instanceof Instant) {
            return (Instant) t;
        }
        throw new RuntimeException("当前日期格式/类型不支持转换");
    }

    /**
     * 将日期/时间转成对应的字符串
     */
    public static <T> String convertToLDStr(T t) {
        if (t instanceof String) {
            return (String) t;
        } else if (t instanceof Date) {
            return convertLDToLDStr(convertToLD((Date) t));
        } else if (t instanceof LocalDate) {// 主
            return convertLDToLDStr((LocalDate) t);
        } else if (t instanceof LocalDateTime) {
            return convertLDToLDStr(convertToLD((LocalDateTime) t));
        } else if (t instanceof Instant) {
            return convertLDToLDStr(convertToLD(((Instant) t)));
        }
        throw new RuntimeException(ERROR_MSG);
    }

    public static <T> String convertToLDTStr(T t) {
        if (t instanceof String) {
            return (String) t;
        } else if (t instanceof Date) {
            return convertLDTToLDTStr(convertToLDT((Date) t));
        } else if (t instanceof LocalDate) {
            return convertLDTToLDTStr(convertToLDT(((LocalDate) t)));
        } else if (t instanceof LocalDateTime) {// 主
            return convertLDTToLDTStr((LocalDateTime) t);
        } else if (t instanceof Instant) {
            return convertLDTToLDTStr(convertToLDT(((Instant) t)));
        }
        throw new RuntimeException(ERROR_MSG);
    }

    /////////////////////------------------------------------------

    private static LocalDate convertStrToLD(String t) {
        return LocalDate.parse(t, DATE_FORMATTER);
    }

    private static LocalDate convertDateToLD(Date date) {
        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
    }

    private static LocalDate convertLDTToLD(LocalDateTime t) {
        return t.toLocalDate();
    }

    private static LocalDate convertInstantToLD(Instant t) {
        return convertLDTToLD(LocalDateTime.ofInstant(t, ZoneId.systemDefault()));
    }

    private static LocalDateTime convertLDToLDT(LocalDate t) {
        return LocalDateTime.of(t, LocalTime.of(0, 0, 0));
    }

    private static LocalDateTime converStrToLDT(String t) {
        return LocalDateTime.parse(t, DATE_TIME_FORMATTER);
    }

    private static LocalDateTime convertDateToLDT(Date date) {
        return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
    }

    private static LocalDateTime convertInstantToLDT(Instant instant) {
        return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
    }

    private static Date convertStrToDate(String t) {
        return convertLDToDate(convertStrToLD(t));
    }

    private static Date convertLDToDate(LocalDate t) {
        return Date.from(t.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
    }

    private static Date convertLDTToDate(LocalDateTime t) {
        return Date.from(t.atZone(ZoneId.systemDefault()).toInstant());
    }

    private static Date convertInstantToDate(Instant t) {
        return Date.from(t);
    }

    private static Instant convertStrToInstant(String t) {
        return LocalDateTime.parse(t, DATE_TIME_FORMATTER).toInstant(ZoneOffset.UTC);
    }

    private static Instant convertDateToInstant(Date t) {
        return Instant.ofEpochMilli(t.getTime());
    }

    private static Instant converLDToInstant(LocalDate t) {
        return t.atTime(LocalTime.MIDNIGHT).atZone(ZoneId.systemDefault()).toInstant();
    }

    private static Instant convertLDTToInstant(LocalDateTime t) {
        return t.atZone(ZoneOffset.ofHours(0)).toInstant();
    }

    private static String convertLDToLDStr(LocalDate t) {
        return t.format(DATE_TIME_FORMATTER);
    }

    private static String convertLDTToLDTStr(LocalDateTime t) {
        return t.format(DATE_TIME_FORMATTER);
    }

}
